import processing.core.*;
import java.util.ArrayList;
import java.util.ListIterator;

public class PathReader extends PGraphics 
{
  protected PMatrix3D              	_curMat = new PMatrix3D();
  protected ArrayList<PMatrix3D>   	_matrixStack = new ArrayList<PMatrix3D>(); 
  int                              	_matrixStackMax = 100;
  int                              	_matrixStackDepth = 0;
  
  protected int						_drawType;

  protected ArrayList<ArrayList<PVector>>	_lineList;
  protected ArrayList<PVector>				_tempLine;
  
  protected ArrayList<PVector>				_pointList;

  PVector _penUpVec = new PVector();

  PVector _minVec = new PVector();
  PVector _maxVec = new PVector();


  float       currX, currY;
  boolean     _startShape = false;
  
  int								_lineIndex;
  int								_circleRes = 5;	// per mm

  public PathReader() 
  {
    super();
    width = 400;
    height = 400;

    currX = 0;
    currY = 0;

	_minVec.set(1000000,1000000,1000000);
	_maxVec.set(-1000000,-1000000,-1000000);

	_lineList = new ArrayList<ArrayList<PVector>>();
	_pointList = new ArrayList<PVector>();

  }
  
  public PVector getMinVec() { return _minVec; }
  public PVector getMaxVec() { return _maxVec; }

  protected void calcMinMax(PVector vec)
  {
	calcMinMax(_minVec,_maxVec,vec);
  }

  protected static void calcMinMax(ArrayList<ArrayList<PVector>> polyList,PVector minVec,PVector maxVec)
  {
	minVec.set(1000000,1000000,1000000);
	maxVec.set(-1000000,-1000000,-1000000);

	ListIterator<PVector> itr;
	ArrayList<PVector> poly;
	ListIterator<ArrayList<PVector>> polyItr = polyList.listIterator();
	PVector vec;

    while(polyItr.hasNext()) 
	{
	  poly = polyItr.next();
	  itr = poly.listIterator();
	  while(itr.hasNext()) 
	  {
		calcMinMax(minVec,maxVec,itr.next());
	  }
	}
  }

  protected static void calcMinMax(PVector minVec,PVector maxVec,PVector vec)
  {
	if(vec.x > maxVec.x)
	  maxVec.x = vec.x;
	else if(vec.x < minVec.x)
	  minVec.x = vec.x;

	if(vec.y > maxVec.y)
	  maxVec.y = vec.y;
	else if(vec.y < minVec.y)
	  minVec.y = vec.y;

	if(vec.z > maxVec.z)
	  maxVec.z = vec.z;
	else if(vec.z < minVec.z)
	  minVec.z = vec.z;
  }

  public void beginDraw()
  {
    _curMat.reset();
    _matrixStack.clear();
    _matrixStackDepth = 0; 
 
    
	_lineList.clear();
	_pointList.clear();

  }
  
  public void endDraw()
  {	
	  // calculate the length of all lines
	
  }

  public ArrayList<ArrayList<PVector>> lineList() 
  {
	return _lineList;
  }

  public ArrayList<PVector> pointList() 
  {
	return _pointList;
  }

  // divide a polygone into small junks
  public static ArrayList<ArrayList<PVector>>  resamplePolygonList(ArrayList<ArrayList<PVector>> polygonList,float minDist)
  {
	ArrayList<ArrayList<PVector>> retList = new ArrayList<ArrayList<PVector>>();

	ListIterator<ArrayList<PVector>> itr = polygonList.listIterator();
    while(itr.hasNext()) 
	{
	  retList.add(resamplePolygon(itr.next(),minDist));
	}	

	return retList;
  }

  public static ArrayList<PVector> resamplePolygon(ArrayList<PVector> polygon,float minDist)
  {
	if(polygon.size() < 2)
	  return polygon;
  
	PVector p1;
	PVector p2;
	ArrayList<PVector> resamplePoly = new ArrayList<PVector>();
	
	ListIterator<PVector> itr = polygon.listIterator();
	p1 = itr.next();
    while(itr.hasNext()) 
	{
      p2 = itr.next();
	  resamplePoly.addAll(resampleLine(p1,p2,minDist,!itr.hasNext()));
	  p1 = p2;
	}
	return resamplePoly;
  }

  public static ArrayList<PVector> resampleLine(PVector p1,PVector p2,float minDist,boolean endFlag)
  {
	ArrayList<PVector> retPoly = new ArrayList<PVector>();
	PVector dist = PVector.sub(p2,p1);
	float length = dist.mag();
	
	if(length < minDist)
	{
	  retPoly.add(p1);
	  retPoly.add(p2);
	}
	else
	{
	  int pieces = (int)(length / minDist);
	  PVector step = PVector.mult(dist,minDist / length);
	  PVector pos = p1.get();

	  retPoly.add(p1.get());

	  if(pieces <= 1)
	  {
		retPoly.add(PVector.add(p1,PVector.mult(dist,0.5f)));
	  }
	  else
	  {
		pos.add(step);
		retPoly.add(pos.get());
	  }

	  for(int i=0; i < pieces-1;i++)
	  {
		pos.add(step);
		retPoly.add(pos.get());
	  }
	  if(endFlag)
		retPoly.add(p2.get());
	}
	return retPoly;
  }

  public static float getPolygonLength(ArrayList<PVector> polygon)
  {
	if(polygon.size() < 2)
	  return 0.0f;
  
	float length = 0.0f;
	PVector p1;
	PVector p2;
	PVector dir;
	ArrayList<PVector> resamplePoly = new ArrayList<PVector>();
	
	ListIterator<PVector> itr = polygon.listIterator();
	p1 = itr.next();
    while(itr.hasNext()) 
	{
      p2 = itr.next();

	  dir = PVector.sub(p1,p2);
	  length += dir.mag();

	  p1 = p2;
	}
	System.out.println("getPolygonLength: " + length);
	return length;
  }

  public static float getPolygonListLength(ArrayList<ArrayList<PVector>> polygonList)
  {
	float length = 0.0f;
	ArrayList<ArrayList<PVector>> retList = new ArrayList<ArrayList<PVector>>();

	ListIterator<ArrayList<PVector>> itr = polygonList.listIterator();
    while(itr.hasNext()) 
	{
	  length += getPolygonLength(itr.next());
	}	
	return length;
  }

/*
  protected ArrayList<PVector> getLine()
  {

	if(_lineIndex >= _lineIndexList.size())
	  return null;

	ArrayList<PVector> line = new ArrayList<PVector>();
	int vertexIndex;
	for(int i = _lineIndex; i < _lineIndexList.size();i++)
	{
	  vertexIndex = _vertexList.get(i).intValue();
	  if(vertexIndex >= 0)
		line.add(_vertexList[vertexIndex]);
	  else 
		break;
	}

	_lineIndex += line.size() + 1;
	return line;
  }
*/



  public static PVector getInterpPos(ArrayList<PVector> polygon,float time)
  {
	  int size = polygon.size();
	  if(size <= 0)
		return new PVector();
/*
	  if(time != 1.0f)
		time = PApplet.abs(time) % 1.0f;
*/
	  if(time >= 1.0f)
		  return polygon.get(polygon.size() - 1).get();
	  else if(time <= 0.0f)
		  return polygon.get(0).get();

	  float	fIndex = (size-1) * time;
	  int	iIndex = (int)fIndex;
	  float	subTime = fIndex-iIndex;

	  PVector pos = polygon.get(iIndex).get();
	  PVector nextPos = polygon.get(iIndex+1).get();
	  PVector dist = PVector.sub(nextPos,pos);

	  return(PVector.add(pos,PVector.mult(dist,subTime)));
  }

  ///////////////////////////////////////////////////////
  // xform methods
  
  public void pushMatrix() 
  {
    if(_matrixStackDepth == _matrixStackMax) 
    {
      throw new RuntimeException(ERROR_PUSHMATRIX_OVERFLOW);
    }
    
    _matrixStack.add(_curMat.get());
    _matrixStackDepth++; 
  }
  
  public void popMatrix() 
  {
     if(_matrixStackDepth == 0) 
     {
        throw new RuntimeException(ERROR_PUSHMATRIX_UNDERFLOW);
     }
     _matrixStackDepth--;
     _curMat.set(_matrixStack.get(_matrixStackDepth));
  }
  
  public void translate(float tx, float ty) 
  {
    translate(tx,ty,0.0f);
  }
  
  public void translate(float tx, float ty,float tz) 
  {
    _curMat.translate(tx,ty,tz);  
	//System.out.println("translate: " + tx + "," + ty+ "," + tz );
  }
  
  public void rotate(float angle) 
  {
    rotate(angle,0.0f,0.0f,1.0f);
  }

  public void rotateX(float angle) 
  {
    rotate(angle,1.0f,0.0f,0.0f);
  }

  public void rotateY(float angle) 
  {
    rotate(angle,0.0f,1.0f,0.0f);
  }

  public void rotateZ(float angle)
  {
    rotate(angle,0.0f,0.0f,1.0f);
  }

  public void rotate(float angle, float vx, float vy, float vz) 
  {
    _curMat.rotate(angle,
                  vx,
                  vy,
                  vz);
  }
  
  public void scale(float x,float y)
  {
    _curMat.scale(x,y,0.0f);
  }
  
  public void scale(float x,float y,float z)
  {
    _curMat.scale(x,y,z);
  }
   
  public void applyMatrix(PMatrix3D source) 
  {
    _curMat.apply(source);
  }
  
  ////////////////////////////////////////////
  // 
  public void penUp(PVector vecUp)
  {
    _penUpVec = vecUp.get();
  }
  

  ////////////////////////////////////////////
  // drawing
  
  public void beginShape() 
  {
    beginShape(POLYGON);

  }

  public void beginShape(int mode) 
  {
	//System.out.println("beginShape: " + mode);

	super.beginShape(mode);
   
	_drawType = mode;
    _startShape = true;
	//_vertexCount = 0;

    switch(mode)
	{
	case PConstants.POINTS:
	  break;
	default:
	  _tempLine = new ArrayList<PVector>();
	  break;
	}
  }

  public void endShape()
  {
    endShape(PConstants.OPEN);
  }

  public void endShape(int mode)
  {
	//System.out.println("endShape");
	super.endShape(mode);

	if(_tempLine == null || _tempLine.size() <= 0)
	  return;

    switch(mode)
	{
	case PConstants.CLOSE:
	  switch(_drawType)
	  {
	  case PConstants.POINTS:
		break;
	  case PConstants.TRIANGLES:
	  case PConstants.TRIANGLE_STRIP:
		if(_tempLine.size() > 1)
		{
		  _lineList.add(_tempLine);
		  _tempLine.clear();
		}
		break;
	  default:
		if(_tempLine.size() > 1)
		{	
		  _tempLine.add(_tempLine.get(0).get());
		  _lineList.add(_tempLine);
		  _tempLine = null;
		}
		break;
	  }
	  break;
	case PConstants.OPEN:
	default:
	  if(isLine(_drawType))
	  {	
		if(_tempLine.size() > 1)
		{
		  _lineList.add(_tempLine);
		  _tempLine = null;
		}
	  }
	  break;
	}

	
  }       

  public void vertex(float x, float y,float z) 
  {
	//System.out.println("vertex");
	super.vertex(x,y,z);

	PVector curVec = _curMat.mult(new PVector(x,y,z),new PVector());
	//System.out.println("curVec=" + curVec);
	switch(_drawType)
	{
	case PConstants.POINTS:
	  _pointList.add(curVec);
	  break;
	case PConstants.TRIANGLES:
	  break;
	case PConstants.TRIANGLE_STRIP:
	default:
	  // thread all like lines
	  _tempLine.add(curVec);
	  break;
	}

	calcMinMax(curVec);
  }

  static boolean isLine(int mode)
  {
  	switch(mode)
	{
	case PConstants.POINTS:
	  return false;
	default:
  	  return true;
	}
  }

  public void vertex(float x, float y) 
  {
    vertex(x,y,0.0f);
  }

  /*
  public void bezierVertex(float x2, float y2, float z2,
                           float x3, float y3, float z3,
                           float x4, float y4, float z4)
  {
	_tempLine = new ArrayList<PVector>();

    bezierInitCheck();
    bezierVertexCheck();
    PMatrix3D draw = bezierDrawMatrix;

    float[] prev = vertices[vertexCount-1];
    float x1 = prev[X];
    float y1 = prev[Y];
    float z1 = prev[Z];

    float xplot1 = draw.m10*x1 + draw.m11*x2 + draw.m12*x3 + draw.m13*x4;
    float xplot2 = draw.m20*x1 + draw.m21*x2 + draw.m22*x3 + draw.m23*x4;
    float xplot3 = draw.m30*x1 + draw.m31*x2 + draw.m32*x3 + draw.m33*x4;

    float yplot1 = draw.m10*y1 + draw.m11*y2 + draw.m12*y3 + draw.m13*y4;
    float yplot2 = draw.m20*y1 + draw.m21*y2 + draw.m22*y3 + draw.m23*y4;
    float yplot3 = draw.m30*y1 + draw.m31*y2 + draw.m32*y3 + draw.m33*y4;

    float zplot1 = draw.m10*z1 + draw.m11*z2 + draw.m12*z3 + draw.m13*z4;
    float zplot2 = draw.m20*z1 + draw.m21*z2 + draw.m22*z3 + draw.m23*z4;
    float zplot3 = draw.m30*z1 + draw.m31*z2 + draw.m32*z3 + draw.m33*z4;

    for (int j = 0; j < bezierDetail; j++) {
      x1 += xplot1; xplot1 += xplot2; xplot2 += xplot3;
      y1 += yplot1; yplot1 += yplot2; yplot2 += yplot3;
      z1 += zplot1; zplot1 += zplot2; zplot2 += zplot3;
      vertex(x1, y1, z1);
    }
	_lineList.add(_tempLine);
  }

  public void quadraticVertex(float cx, float cy,
                              float x3, float y3) 
  {
    float[] prev = vertices[vertexCount-1];
    float x1 = prev[X];
    float y1 = prev[Y];

    bezierVertex(x1 + ((cx-x1)*2/3.0f), y1 + ((cy-y1)*2/3.0f),
                 x3 + ((cx-x3)*2/3.0f), y3 + ((cy-y3)*2/3.0f),
                 x3, y3);
  }
  */

  public void bezier(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) 
  {
    //System.out.println("\tbezier:");
  }

  public void bezier(float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, float z3, float x4, float y4, float z4) 
  {
    //System.out.println("\tbezierShape:");
  }

  public void ellipse(float posX, 
                      float posY, 
                      float w, 
                      float h)
  {
	_tempLine = new ArrayList<PVector>();

	PVector curVec;

	float angle, step;
	float x, y;
	float radiusX = w / 2.0f;
	float radiusY = h / 2.0f;
	//step = PApplet.radians(3);
	float count = PApplet.max(PConstants.TWO_PI * w,PConstants.TWO_PI * h) / _circleRes;
	if(count <3.0f)
	  count = 3.0f;
	step = PConstants.TWO_PI / count;

	for( angle = 0; angle < PConstants.TWO_PI; angle += step )
	{
	  x = posX + radiusX * PApplet.cos( angle ) ;
	  y = posY + radiusY * PApplet.sin( angle ) ;

	  curVec = _curMat.mult(new PVector(x ,y,0),new PVector());
	  _tempLine.add(curVec);
	  calcMinMax(curVec);

	}
	_tempLine.add(_tempLine.get(0));
	_lineList.add(_tempLine);
	
  }

  public void beginContour()
  {
	_startShape = true;
	//_vertexCount = 0;

    switch(shape)
	{
	case PConstants.POINTS:
	  break;
	default:
	  _tempLine = new ArrayList<PVector>();
	  break;
	}
  }


  public void endContour()
  {
	if(isLine(_drawType) && _tempLine != null &&  _tempLine.size() > 1)
	{	
	  switch(_drawType)
	  {
	  case PConstants.CLOSE:  
		_tempLine.add(_tempLine.get(0).get());
		_lineList.add(_tempLine);
		_tempLine = null;
		break;
	  case PConstants.OPEN:
	  default:
		_lineList.add(_tempLine);
		_tempLine = null;
		break;
	  }
	}
  }


}

